//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace OpenLS.UI
{

    public class FlowPanel : Panel
    {
        public static Style NoMarginStyle
        {
            get
            {
                if (_noMarginStyle == null)
                {
                    _noMarginStyle = new Style();
                    _noMarginStyle.Setters.Add(new Setter(LineMarginProperty, 0.0d));
                }
                return _noMarginStyle;
            }
        }
        static Style _noMarginStyle;

        public static Style MarginStyle
        {
            get
            {
                if (_marginStyle == null)
                {
                    _marginStyle = new Style();
                    _marginStyle.Setters.Add(new Setter(LineMarginProperty, 7.0d));
                }
                return _marginStyle;
            }
        }
        static Style _marginStyle;
        protected override Size ArrangeOverride(Size finalSize)
        {
            //\\ Debug.WriteLine("ArrangeOverride " + GetType());

            ElementLineCollection lines = GetLines(finalSize);
            double y = LineMargin;
            foreach (ElementLine line in lines)
            {
                double x = 0;
                foreach (UIElement element in line)
                {
                    element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
                    x += element.DesiredSize.Width;
                }
                y += line.Height + LineMargin;
            }
            return finalSize;
        }
        protected override Size MeasureOverride(Size availableSize)
        {
            //\\Debug.WriteLine("MeasureOverride " + GetType());
            foreach (UIElement element in InternalChildren)
            {
                element.Measure(availableSize);
            }
            ElementLineCollection lines = GetLines(availableSize);
            Size result = new Size();
            foreach (ElementLine l in lines)
            {
                result.Width = Math.Max(result.Width, l.Width);
                result.Height += l.Height;
            }
            if (!double.IsInfinity(availableSize.Height))
                result.Height = availableSize.Height;

            return result;


        }


        public double LineMargin
        {
            get { return (double)GetValue(LineMarginProperty); }
            set { SetValue(LineMarginProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineMargin.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineMarginProperty =
            DependencyProperty.Register("LineMargin", typeof(double), typeof(FlowPanel), new UIPropertyMetadata(7.0d));



        private ElementLineCollection GetLines(Size availableSize)
        {
            ElementLineCollection lines = new ElementLineCollection(LineMargin);
            int index = 0;
            foreach (UIElement element in InternalChildren)
            {
                lines.Add(new ElementLine(index++, element));
            }
            while (lines.GetTotalHeight() > availableSize.Height && lines.Count > 1)
            {
                lines.Sort(delegate(ElementLine l1, ElementLine l2)
                    {
                        return l1.Width.CompareTo(l2.Width);
                    });
                ElementLine lineToDelete = lines[0];
                lines.Sort(delegate(ElementLine l1, ElementLine l2)
                                    {
                                        return l1.Index.CompareTo(l2.Index);
                                    });
                int indexToDelete = lines.IndexOf(lineToDelete);
                ElementLine previousLine = null;
                ElementLine nextLine = null;
                if (indexToDelete > 0)
                    previousLine = lines[indexToDelete - 1];
                if (indexToDelete < lines.Count - 1)
                    nextLine = lines[indexToDelete + 1];
                lines.MoveLine(lineToDelete, previousLine, nextLine);

            }
            lines.Optimize();
            return lines;
        }


        class ElementLineCollection: List<ElementLine>
        {
            public void Optimize()
            {
                while (OptimizeStep()) ;
            }
            bool OptimizeStep()
            {
                double maxWidth = 0;
                foreach (ElementLine line in this)
                {
                    maxWidth = Math.Max(line.Width, maxWidth);
                }
                for (int i = 0; i < Count; i++)
                {
                    ElementLine l = this[i];
                    if (l.Width < maxWidth)
                        continue;
                    if (l.Count > 1)
                    {
                        if (i > 0)
                        {
                            ElementLine previousLine = this[i - 1];
                            UIElement elementToMove = l[0];
                            if (previousLine.Width + elementToMove.DesiredSize.Width < maxWidth)
                            {
                                previousLine.Add(elementToMove);
                                l.RemoveAt(0);
                                return true;
                            }
                        }
                        if (i < Count - 1)
                        {
                            ElementLine nextLine = this[i + 1];
                            UIElement elementToMove = l[l.Count - 1];
                            if (nextLine.Width + elementToMove.DesiredSize.Width < maxWidth)
                            {
                                nextLine.AddFirst(elementToMove);
                                l.RemoveAt(l.Count - 1);
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
            public ElementLineCollection(double lineMargin)
            {
                LineMargin = lineMargin;
            }

            readonly double LineMargin;
            public double GetTotalHeight()
            {
                double result = 0;
                foreach (ElementLine line in this)
                    result += line.Height;
                return result + LineMargin * (Count + 1);
            }
            public void MoveLine(ElementLine lineToDelete, ElementLine previousLine, ElementLine nextLine)
            {
                Remove(lineToDelete);
                if (previousLine == null)
                {
                    foreach (UIElement element in lineToDelete.GetElementsFromEnd())
                        nextLine.AddFirst(element);
                    lineToDelete.Clear();
                    return;
                }
                if (nextLine == null)
                {
                    foreach (UIElement element in lineToDelete)
                        previousLine.Add(element);
                    lineToDelete.Clear();
                    return;
                }
                while (lineToDelete.Count > 0)
                {
                    if (previousLine.Width < nextLine.Width)
                    {
                        previousLine.Add(lineToDelete[0]);
                        lineToDelete.RemoveFirst();
                    }
                    else
                    {
                        nextLine.AddFirst(lineToDelete[lineToDelete.Count - 1]);
                        lineToDelete.RemoveLast();
                    }
                }
            }

        }
        class ElementLine: List<UIElement>
        {
            public ElementLine(int index, UIElement element)
            {
                _index = index;
                Add(element);
            }
            private readonly int _index;
            public int Index
            {
                get { return _index; }
            }

            public double Width
            {
                get
                {
                    double result = 0;
                    foreach (UIElement e in this)
                        result += e.DesiredSize.Width;
                    return result;
                }
            }
            public double Height
            {
                get
                {
                    double result = 0;
                    foreach (UIElement e in this)
                        result = Math.Max(e.DesiredSize.Height, result);
                    return result;
                }
            }


            internal IEnumerable<UIElement> GetElementsFromEnd()
            {
                for (int i = Count - 1; i >= 0; i--)
                    yield return this[i];
            }

            internal void AddFirst(UIElement element)
            {
                Insert(0, element);
            }




            internal void RemoveFirst()
            {
                RemoveAt(0);
            }
            internal void RemoveLast()
            {
                RemoveAt(Count - 1);
            }
        }
    }
}

